#ifndef _ROOM_HPP_
#define _ROOM_HPP_
#include "configs.hpp"
#include "database.hpp"
#include "onlineUser.hpp"
#include <mutex>
#include <unordered_map>
namespace gomoku
{

/*
    房间类，负责玩家对战胜负的记录、聊天动作的处理等，
    总之就是房间内任何动作都需要广播给所有在房间内的用户。
*/
/////////////////// Start of Class Room //////////////////////////////
class Room
{
    enum class room_status
    {
        GAME_START,
        GAME_OVER
    };

private:
    uint64_t _rid;                         // 房间id
    int _playerCnt;                        // 玩家人数
    uint64_t _whiteUid;                    // 白棋玩家id
    uint64_t _blackUid;                    // 黑棋玩家id
    room_status _status;                   // 房间状态
    UserTable *_ut;                        // 用户表管理模块
    OnlineUser *_ou;                       // 在线用户管理模块
    std::vector<std::vector<char>> _board; // 棋盘

public:
    Room(uint64_t rid, UserTable *ut, OnlineUser *ou);
    ~Room();

    /*总地处理玩家的请求*/
    void HandleRequest(const Json::Value &req);
    /*处理玩家退出房间动作*/
    void HandleExitRoom(uint64_t uid);

private:
    /*处理下棋动作*/
    Json::Value HandleChess(const Json::Value &req);

    /*处理聊天动作*/
    Json::Value HandleChat(const Json::Value &req);

    /*处理再来一局动作*/
    Json::Value HandleStartAgain(const Json::Value &req);

    /*广播给房间内所有玩家*/
    void Broadcast(const Json::Value &rsp);

private:
    /*判断是否有五子连珠*/
    bool __fiveChess(int row, int col, int row_offset, int col_offset, char color);
    /*从(row,col)位置开始往8个方向扫描，判断是否有胜利者*/
    uint64_t __win(int row, int col, char color);

public:
    inline uint64_t GetRid() { return _rid; }
    inline room_status GetStatus() { return _status; }
    inline int GetPlayerCnt() { return _playerCnt; }
    inline uint64_t GetWhiteUid() { return _whiteUid; }
    inline uint64_t GetBlackUid() { return _blackUid; }
    inline void SetWhiteUid(uint64_t uid)
    {
        _whiteUid = uid;
        _playerCnt++;
    }
    inline void SetBlackUid(uint64_t uid)
    {
        _blackUid = uid;
        _playerCnt++;
    }

}; /////////////////// End of Class Room //////////////////////////////

using room_ptr = std::shared_ptr<Room>;

/*房间管理类：负责创建房间、查找房间、销毁房间等与房间相关的工作。*/
class RoomManager
{
private:
    UserTable *_userTable;                         // 用户数据管理模块的句柄
    OnlineUser *_onlineUser;                       // 在线用户管理模块的句柄
    uint64_t _nextRid = 1;                         // rid分配器
    std::mutex _mtx;                               // 保护临界资源的互斥锁
    std::unordered_map<uint64_t, room_ptr> _rooms; // rid和房间对象句柄的映射关系 --- 通过rid找具体的房间对象
    std::unordered_map<uint64_t, uint64_t> _users; // uid和rid之间的映射关系 --- 通过uid找rid再找具体的房间对象

public:
    RoomManager(UserTable *ut, OnlineUser *olu);

    /*通过rid找房间对象*/
    room_ptr GetRoomByRid(uint64_t rid);

    /*通过uid找房间对象*/
    room_ptr GetRoomByUid(uint64_t uid);

    /*通过rid销毁房间*/
    void DestroyRoom(uint64_t rid);

    /*删除房间中指定用户*/
    void RemoveUser(uint64_t uid);

    /*给两个匹配成功的用户创建房间*/
    room_ptr CreateRoomForTwoUser(uint64_t uid1, uint64_t uid2);
};








































Room::Room(uint64_t rid, UserTable *ut, OnlineUser *ou)
    : _rid(rid), _status(room_status::GAME_START), _playerCnt(0), _ut(ut), _ou(ou), _board(BOARD_ROW, std::vector<char>(BOARD_COL, 0))
{
    mylog::getLogger(LOGGER_NAME)->wlog("创建房间成功，rid = %lu", _rid);
}

Room::~Room()
{
    mylog::getLogger(LOGGER_NAME)->wlog("销毁房间成功，rid = %lu", _rid);
}

/*
    处理在游戏房间的请求：
    聊天请求：
        {
            "optype": "chat",
            "room_id": 222,
            "uid": 1,
            "message": "赶紧点"
        }
    聊天响应：
        {
            "optype": "chat",
            "result": false
            "reason": "聊天失败具体原因....比如有敏感词..."
        }
        {
            "optype": "chat",
            "result": true,
            "room_id": 222,
            "uid": 1,
            "message": "赶紧点"
        }
    下棋请求：
        {
            "optype": "put_chess", // put_chess表示当前请求是下棋操作
            "room_id": 222, // room_id 表示当前动作属于哪个房间
            "uid": 1, // 当前的下棋操作是哪个用户发起的
            "row": 3, // 当前下棋位置的行号
            "col": 2 // 当前下棋位置的列号
        }
    下棋响应：
        {
            "optype": "put_chess",
            "result": false
            "reason": "走棋失败具体原因...."
        }
        {
            "optype": "put_chess",
            "result": true,
            "reason": "对方掉线，不战而胜！" / "对方/己方五星连珠，战无敌/虽败犹荣！",
            "room_id": 222,
            "uid": 1,
            "row": 3,
            "col": 2,
            "winner": 0 // 0-未分胜负， !0-已分胜负 (uid是谁，谁就赢了)
        }
*/
void Room::HandleRequest(const Json::Value &req)
{
    // 1.判断房间号是否一致
    Json::Value rsp;
    uint64_t rid = req["room_id"].asUInt64();
    if (rid != _rid)
    {
        rsp["optype"] = req["optype"].asString();
        rsp["result"] = false;
        rsp["reason"] = "房间号不一致";
        Broadcast(rsp);
        return;
    }

    // 2.根据不同请求调用不同的处理函数，最后广播出去
    if (req["optype"].asString() == "put_chess") // 处理下棋
    {
        rsp = HandleChess(req);

        // 有winner，更新数据库数据
        if (rsp["winner"].asUInt64() != 0)
        {
            uint64_t winner = rsp["winner"].asUInt64();
            uint64_t loser = (winner == _whiteUid) ? _blackUid : _whiteUid;
            _ut->Win(winner);
            _ut->Lose(loser);
            _status = room_status::GAME_OVER;
        }
    }
    else if (req["optype"].asString() == "chat") // 处理聊天
    {
        rsp = HandleChat(req);
    }
    else if (req["optype"].asString() == "start_again") // 处理「再来一局」
    {
        rsp = HandleStartAgain(req);
    }
    else // 未知的请求
    {
        rsp["optype"] = req["optype"].asString();
        rsp["result"] = false;
        rsp["reason"] = "未知的请求";
        return Broadcast(rsp);
    }
    // 3.把响应广播给所有玩家
    std::string rsp_str;
    util::json::serialize(rsp, rsp_str);
    Broadcast(rsp);
}
void Room::HandleExitRoom(uint64_t uid)
{
    // 1.若下棋过程中玩家退出，则另一玩家获胜
    //   若下棋结束后退出，则正常退出
    Json::Value rsp;
    if (_status == room_status::GAME_START)
    {
        uint64_t winnerid = (uid == _whiteUid ? _blackUid : _whiteUid);
        uint64_t loserid = (winnerid == _whiteUid ? _blackUid : _whiteUid);
        rsp["optype"] = "put_chess";
        rsp["result"] = true;
        rsp["reason"] = "对方掉线，你胜利了";
        rsp["room_id"] = _rid;
        rsp["uid"] = uid;
        rsp["row"] = -1;
        rsp["col"] = -1;
        rsp["winner"] = winnerid;

        // 更新数据库用户信息
        _ut->Win(winnerid);
        _ut->Lose(loserid);
        _status = room_status::GAME_OVER;
    }
    Broadcast(rsp);

    // 2.房间玩家数量-1
    _playerCnt -= 1;
}

Json::Value Room::HandleChess(const Json::Value &req)
{
    /*
        待优化，减少重复代码。。
    */
    Json::Value rsp = req;

    // 2.判断玩家是否在线，有人不在线，则另一个人获胜
    int req_uid = req["uid"].asInt64();
    int row = req["row"].asInt();
    int col = req["col"].asInt();
    if (_ou->InRoom(_whiteUid) == false)
    {
        rsp["result"] = true;
        rsp["reason"] = "对方掉线，你胜利了";
        rsp["winner"] = _blackUid;
        return rsp;
    }
    if (_ou->InRoom(_blackUid) == false)
    {
        rsp["result"] = true;
        rsp["reason"] = "对方掉线，你胜利了";
        rsp["winner"] = _whiteUid;
        return rsp;
    }
    // 3.判断下棋位置是否合理，合理则下棋
    if (_board[row][col] != 0)
    {
        rsp["result"] = false;
        rsp["reason"] = "当前位置已经有棋子了";
        return rsp;
    }
    // 下棋
    char req_color = (req_uid == _whiteUid) ? WHITE_CHESS : BLACK_CHESS;
    _board[row][col] = req_color;

    // 4.判断下完棋后，是否有人胜利(五子连珠)
    uint64_t winner = __win(row, col, req_color);

    rsp["result"] = true;
    if (winner)
        rsp["reason"] = "五子连珠，你赢啦！";
    else
        rsp["reason"] = "继续下棋";
    rsp["winner"] = winner;
    return rsp;
}

Json::Value Room::HandleChat(const Json::Value &req)
{
    Json::Value rsp = req;
    rsp["result"] = true;
    return rsp;
}

Json::Value Room::HandleStartAgain(const Json::Value &req)
{
    auto log = mylog::getLogger(LOGGER_NAME);
    log->dlog("处理再来一局请求");

    Json::Value rsp;

    // 1.判断另一个玩家是否在游戏房间内
    uint64_t opponent = (req["uid"].asInt64() == _whiteUid) ? _blackUid : _whiteUid;
    if(_ou->InRoom(opponent) == false)
    {
        rsp["optype"] = "start_again";
        rsp["result"] = false;
        rsp["reason"] = "再来一局失败";
        rsp["room_id"] = _rid;
        rsp["white_id"] = _whiteUid;
        rsp["black_id"] = _blackUid;
        return rsp;
    }
    
    // 1.将棋盘数据清空
    for(std::vector<char>& v : _board)
        for(char& ch : v)
            ch = 0;

    // 2.切换玩家黑白棋
    std::swap(_whiteUid, _blackUid);

    // 3.返回成功消息
    rsp["optype"] = "start_again";
    rsp["result"] = true;
    rsp["reason"] = "再来一局";
    rsp["room_id"] = _rid;
    rsp["white_id"] = _whiteUid;
    rsp["black_id"] = _blackUid;
    return rsp;
}

void Room::Broadcast(const Json::Value &rsp)
{
    auto log = mylog::getLogger(LOGGER_NAME);

    // 1.序列化json格式的rsp为字符串
    std::string body;
    util::json::serialize(rsp, body);
    // 2.获取房间所有用户的连接并发送
    wsserver_t::connection_ptr conn1 = _ou->GetConnFromRoom(_whiteUid);
    wsserver_t::connection_ptr conn2 = _ou->GetConnFromRoom(_blackUid);
    if (conn1.get())
        conn1->send(body);
    else
        log->elog("白棋玩家获取连接失败");
    if (conn2.get())
        conn2->send(body);
    else
        log->elog("黑棋玩家获取连接失败");
}

bool Room::__fiveChess(int row, int col, int row_offset, int col_offset, char color)
{
    int cnt = 1;
    for (int i = 0; i < 2; ++i) // 判断两个方向
    {
        int r = row + row_offset;
        int c = col + col_offset;
        while (r >= 0 && r < BOARD_ROW && c >= 0 && c < BOARD_COL && _board[r][c] == color)
        {
            ++cnt;
            r += row_offset;
            c += col_offset;
        }
        row_offset *= -1;
        col_offset *= -1;
    }
    if (cnt >= 5)
        return true;
    else
        return false;
}
uint64_t Room::__win(int row, int col, char color)
{
    if (
        __fiveChess(row, col, 0, 1, color) ||  // 行不变，列++/--，判断行
        __fiveChess(row, col, 1, 0, color) ||  // 列不变，行++/--，判断列
        __fiveChess(row, col, -1, 1, color) || // 判断两个斜线
        __fiveChess(row, col, -1, -1, color))
    {
        return color == WHITE_CHESS ? _whiteUid : _blackUid;
    }
    return 0; // 返回0表示没有人获胜
}

RoomManager::RoomManager(UserTable *ut, OnlineUser *olu)
    : _userTable(ut), _onlineUser(olu)
{
    mylog::getLogger(LOGGER_NAME)->dlog("房间管理模块初始化完成了喵~");
}

room_ptr RoomManager::GetRoomByRid(uint64_t rid)
{
    std::unique_lock<std::mutex> lock(_mtx);
    auto it = _rooms.find(rid);
    if (it == _rooms.end())
        return room_ptr();
    return it->second;
}

room_ptr RoomManager::GetRoomByUid(uint64_t uid)
{
    std::unique_lock<std::mutex> lock(_mtx);
    // 先找rid
    auto it = _users.find(uid);
    if (it == _users.end())
        return room_ptr();
    uint64_t rid = it->second;
    // 通过rid找房间对象
    auto it2 = _rooms.find(rid);
    if (it2 == _rooms.end())
        return room_ptr();
    return it2->second;
}
void RoomManager::DestroyRoom(uint64_t rid)
{
    // 移除用户管理和房间管理哈希表中的数据
    room_ptr rp = GetRoomByRid(rid);
    if (rp.get() == nullptr)
        return;
    uint64_t uid1 = rp->GetBlackUid();
    uint64_t uid2 = rp->GetWhiteUid();
    std::unique_lock<std::mutex> lock(_mtx);
    _users.erase(uid1);
    _users.erase(uid2);
    _rooms.erase(rid);
}
void RoomManager::RemoveUser(uint64_t uid)
{
    room_ptr rp = GetRoomByUid(uid);
    if (rp.get() == nullptr)
        return;
    rp->HandleExitRoom(uid);
    if (rp->GetPlayerCnt() == 0)
    {
        DestroyRoom(rp->GetRid());
    }
}
room_ptr RoomManager::CreateRoomForTwoUser(uint64_t uid1, uint64_t uid2)
{
    auto log = mylog::getLogger(LOGGER_NAME);

    // 1. 判断两个玩家是否都在游戏大厅中
    if (_onlineUser->InHall(uid1) == false)
    {
        log->elog("用户不在游戏大厅中，uid: %lu", uid1);
        return room_ptr();
    }
    if (_onlineUser->InHall(uid2) == false)
    {
        log->elog("用户不在游戏大厅中，uid: %lu", uid2);
        return room_ptr();
    }

    // 2. 创建房间并将用户信息添加到房间中
    std::unique_lock<std::mutex> lock(_mtx);
    room_ptr rp(new Room(_nextRid, _userTable, _onlineUser));
    rp->SetWhiteUid(uid1);
    rp->SetBlackUid(uid2);

    // 3. 将房间信息用哈希表管理起来
    _rooms.insert({_nextRid, rp});
    _users.insert({uid1, _nextRid});
    _users.insert({uid2, _nextRid});
    _nextRid++;
    return rp;
}
}
#endif